package com.weihua.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.intelligt.modbus.jlibmodbus.utils.DataUtils;
import com.weihua.bo.MachineBo;
import com.weihua.bo.PlcBo;
import com.weihua.bo.SourceDataBo;
import com.weihua.conver.PlcDataConvert;
import com.weihua.modbus.*;
import com.weihua.po.MachinePo;
import com.weihua.po.PlcPo;
import com.weihua.util.Jackson;
import com.weihua.util.ScheduledUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Service
@Slf4j
public class ModbusService {

    private static Map<String, Map<String, IView>> views = new ConcurrentHashMap<>();

    private static Map<String, ModbusService> serviceMap = new ConcurrentHashMap<>();

    private static Vector<String> machines = new Vector<>();

    @Autowired
    private MachineService machineService;

    @Autowired
    private ModbusClient modbusClient;

    @Autowired
    private PlcService plcService;

    @Autowired
    private SourceDataService sourceDataService;

    @Autowired
    private PlcDataService plcDataService;


    public final static String GLOBLE_DB_VIEW = "GLOBLE_DB_VIEW";

    public static void saveData(List<PlcBo> bos) {
        ModbusService service = serviceMap.get(ModbusService.class.getName());
        service.savePlcDetail(bos);
    }

    /**
     * 将数据进行数据库存储
     * @param bos
     */
    private void savePlcDetail(List<PlcBo> bos) {
        plcDataService.saveBatch(plcDataService.getBos(bos), 100);
    }

    public void addMonitor(){
        List<MachineBo> machines = machineService.list(new LambdaQueryWrapper<MachinePo>());
        if (CollectionUtils.isNotEmpty(machines)){
            machines.forEach(item -> {
                if (StringUtils.hasText(item.getIp())){
                    addMonitor(item);
                }
            });
        }
    }

    public void addMonitor(MachineBo bo){
        if (!machines.contains(bo.getMachinePk())){
            serviceMap.put(ModbusService.class.getName(), this);
            modbusClient.get(bo);
            schedule(bo);
            machines.add(bo.getMachinePk());
        }
    }

    private void schedule(MachineBo bo) {
        Runnable runnable = () -> {
            List<PlcBo> bos = plcService.list(new LambdaQueryWrapper<PlcPo>());
            ModbusContext context = new ModbusContext(bo.getSlave(), ModbusClient.masterMap.get(bo.getMachinePk()));
            context.clearFunData();
            context.getFunData().put("F01", Arrays.asList(context.readCoils(0,10)));
            context.getFunData().put("F02", Arrays.asList(context.readDiscreteInputs(0,10)));
            context.getFunData().put("F03", Arrays.asList(context.readHoldingRegisters(0,10)));
            context.getFunData().put("F04", Arrays.asList(context.readInputRegisters(0,10)));
            //将数据进行原始保存
            saveSourceDate(context.getFunData());
            if (views.get(bo.getMachinePk())!=null){
                parse(bos, context.getFunData());
                views.get(bo.getMachinePk()).forEach((key, view) -> {
                    if (view!=null){
                        view.setBos(bos); //将解析出来的数据进行填充
                        view.update();//更新数据库或前端数据结果
                    }
                });
            }
        };
        ScheduledUtil.scheduleAtFixedRate(runnable, 1000);
    }

    private void saveSourceDate(Map<String, List> funData) {
        funData.forEach((key, value) -> {
            SourceDataBo bo = new SourceDataBo();
            bo.setFunNo(key);
            bo.setValue(Jackson.writeValue(value));
            sourceDataService.save(bo);
        });
    }

  /**
     * 将功能对象中的值进行解析
     * @param bos
     * @param funData
     */
    private void parse(List<PlcBo> bos, Map<String, List> funData) {
        parseF01(bos, funData.get("F01"), "F01");
        parseF01(bos, funData.get("F02"), "F02");
        parseF02(bos, funData.get("F03"), "F03");
        parseF02(bos, funData.get("F04"), "F03");
    }
    private void parseF01(List<PlcBo> bos, List data, String type){
        bos.forEach(item->{
            if (item.getFunNo().equals(type)){
                item.setValue(Boolean.getBoolean(data.get(item.getOffset()).toString())?"1":"0");
            }
        });
    }

    private void parseF02(List<PlcBo> bos, List data, String type){
        bos.forEach(item->{
            if(item.getFunNo().equals(type)){
                if (item.getType().equals("INT")){
                    item.setValue(data.get(item.getOffset()).toString());
                }else if(item.getType().equals("BIT")){
                    byte[] bytes = DataUtils.toByteArray(Integer.valueOf(data.get(item.getOffset()).toString()));
                    byte tmp = bytes[0];
                    bytes[0] = bytes[1];
                    bytes[1] = tmp;
                    boolean[] bs = DataUtils.toBitsArray(bytes, 16);
                    item.setValue(bs[item.getLocal()]?"1":"0");
                }else if (item.getType().equals("FLOAT")){
                    byte[] bs = new byte[4];
                    byte[] firstBs = DataUtils.toByteArray(Integer.valueOf(data.get(item.getOffset()).toString()));
                    byte[] secondBs = DataUtils.toByteArray(Integer.valueOf(data.get(item.getOffset()).toString())+1);
                    bs[0] = secondBs[1];
                    bs[1] = secondBs[0];
                    bs[2] = firstBs[1];
                    bs[3] = firstBs[0];
                    item.setValue(DataUtils.toFloat(bs)+"");
                }
            }
        });
    }
    public synchronized void addView(String key,IView view){
        if (views.get(view.getParam().getMachinePk())!=null){
            views.get(view.getParam().getMachinePk()).put(key, view);
        }else{
            Map<String, IView> vs = new HashMap<>();
            vs.put(key, view);
            views.put(view.getParam().getMachinePk(), vs);
        }
    }

    public synchronized void removeView(IView view){
        if(views.get(view.getParam().getMachinePk())!=null){
            views.get(view.getParam().getMachinePk()).remove(view.getSessionKey());
        }
    }
}
